using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using PropertyExpression.Common;

namespace PropertyExpression.ReactiveRelation
{
    internal static class InternalUtil
    {
        public static string GetPropertyName<TProperty, TEntity>(
            this IPropertyExpressionProxy<TEntity, TProperty> propertyExpressionProxy)
        {
            return propertyExpressionProxy.GetPropertyInfo().Name;
        }

        public static TProperty GetValue<TProperty, TEntity>(
            this IValueInfo<TProperty, TEntity> valueInfo)
        {
            valueInfo.PropertySettor.SetPropertyValue(valueInfo.EntityPropertyProxy);
            //TODO: improve performance
            return valueInfo.PropertyExpressionProxy.Execute(valueInfo.Entity);
        }

        public static IEnumerable<object> GetReverseEntities<TEntity, TProperty>(
            this IPropertyExpressionProxy<TEntity, TProperty> propertyExpressionProxy,
            object sender)
        {
            foreach (var result in
                (IEnumerable)propertyExpressionProxy.GetPropertyInfo().
                                 GetReversePropertyInfo(propertyInfo => propertyInfo.PropertyType)
                                 .GetValue(sender, null))
            {
                yield return result;
            }
        }

        internal const char Separator = ',';

        internal static PropertyInfo GetReversePropertyInfo(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetReversePropertyInfo(info => GenericArgument(info.PropertyType));
        }

        internal static IOptionalValue<AssociationAttribute> GetAssociationAttribute(
            this ICustomAttributeProvider propertyInfo)
        {
            var propertyAttributes = propertyInfo
                .GetCustomAttributes(typeof (AssociationAttribute), true);
            switch (propertyAttributes.Length)
            {
                case 1:
                    return((AssociationAttribute)propertyAttributes[0]).AsOptionalResult();
                case 0:
                    return OptionalValue.NoValue<AssociationAttribute>();
                default:
                    throw new ApplicationException();
            }
        }

        internal static void ProcessPropertyChanged<T>(
            this T obj,
            Action<object, PropertyChangedEventArgs, object> action)
            where T : INotifyPropertyChanged, INotifyPropertyChanging
        {
            object previousValue = null;

            obj.PropertyChanging +=
                (sender, propertyChangingEventArgs) =>
                sender.GetPropertyInfoFromPropertyChangingEvent(
                    propertyInfo => previousValue = propertyInfo.GetValue(sender, null));

            obj.PropertyChanged +=
                (sender, propertyChangedEventArgs) =>
                    {
                        try
                        {
                            action(sender, propertyChangedEventArgs, previousValue);
                        }
                        finally
                        {
                            previousValue = null;
                        }
                    };            
        }

        internal static TResult ProcessOneToManyCollectionNewItem<TResult, TEntity, TProperty>(
            this IPropertyExpressionProxy<TEntity, TProperty> propertyExpressionProxy, 
            Func<TEntity> entityFunc, 
            IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor, 
            Func<object, TResult> addEntityFunc, 
            Func<object, TResult> removeEntityFunc, 
            Func<TResult> notExistFunc)
        {
            return oneToManyCollectionNewItemProcessor.Process().Execute(
                oneToManyCollectionNewItemProvider =>
                    {
                        var propertyInfo = propertyExpressionProxy.GetReversePropertyInfo();
                        var sender = oneToManyCollectionNewItemProvider.Sender;
                        var entity = entityFunc();
                        if (!ReferenceEquals(entity, null)
                            && propertyInfo.DeclaringType.IsInstanceOfType(sender) 
                            && propertyInfo.Name == oneToManyCollectionNewItemProvider.PropertyName)
                        {
                            if (ReferenceEquals(propertyInfo.GetValue(sender, null), entity))
                            {
                                return addEntityFunc(sender);
                            }
                            if (ReferenceEquals(propertyInfo.GetValue(sender, null), null)
                                && ReferenceEquals(entity, oneToManyCollectionNewItemProvider.PreviousValue))
                            {
                                return removeEntityFunc(sender);
                            }
                        }
                        return notExistFunc();
                    },
                notExistFunc);
        }

        internal static bool IsEntitySet(this Type type)
        {
            if (type.IsGenericType)
            {
                if (typeof (EntitySet<>).Equals(type.GetGenericTypeDefinition()))
                {
                    return true;
                }
            }
            return false;
        }

        internal static Type GenericArgument(Type type)
        {
            return type.GetGenericArguments()[0];
        }

        private static void GetPropertyInfoFromPropertyChangingEvent(
            this object sender, Action<PropertyInfo> action)
        {
            //TODO: improve performance
            var stackTrace = new StackTrace();
            for(var i = 2;;i++)
            {
                var frame = stackTrace.GetFrame(i);
                if (frame == null)
                {
                    throw new ApplicationException();
                }
                if (frame.GetMethod().Name == "SendPropertyChanging")
                {
                    var methodName = stackTrace.GetFrame(i + 1).GetMethod().Name;
                    if (methodName.StartsWith("set_"))
                    {
                        var propertyInfo = sender.GetType().GetProperty(methodName.Substring(4));
                        if (propertyInfo != null)
                        {
                            action(propertyInfo);
                        }
                    }
                    break;
                }
            }
        }

        private static PropertyInfo GetReversePropertyInfo(
            this PropertyInfo propertyInfo, Func<PropertyInfo, Type> typeFunc)
        {
            //TODO: improve performance
            var associationAttributeName = propertyInfo.GetAssociationAttribute().Execute(
                associationAttribute => associationAttribute, 
                () => { throw new ApplicationException(); }).Name;
            foreach (var info in typeFunc(propertyInfo).GetProperties())
            {
                var associationAttribute = info.GetAssociationAttribute();
                if (associationAttribute.HasValue
                    && associationAttributeName == associationAttribute.Value.Name
                    && propertyInfo.Name != info.Name)
                {
                    return info;
                }
            }
            throw new ApplicationException();
        }
    }
}